package org.zt.java.dal.config;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.zt.java.dal.config.vo.ApplicationInfo;
import org.zt.java.dal.config.vo.DataSourceConfig;
import org.zt.java.dal.config.vo.DataSourceConfigItem;
import org.zt.java.dal.config.vo.MethodRouterConfig;
import org.zt.java.dal.datasource.DataSourceType;
import org.zt.java.dal.util.XmlUtil;



/**
 * dal本地文件解析器
 * @author 周涛
 *
 */
public class DalConfigXmlParser {

	public DalConfig parse(InputStream is) {
		try {
			Document document = XmlUtil.doc(is);
			// 格式校验
			validateXml(document);

			// 解析组装对象
			DalConfig dalConfig = new DalConfig();
			parseApplicationInfo(document, dalConfig);
			parseDatasourceConfig(document, dalConfig);
			parseMethodRouteConfigMap(document, dalConfig);

			// 业务合法性校验
			validateDalConfig(dalConfig);
			return dalConfig;
		} catch (IllegalArgumentException ex) {
			throw ex;
		} catch (Exception ex) {
			throw new IllegalArgumentException("Dal config format is error！", ex);
		} finally {
			if (is != null) {
				try {
					is.close();
				} catch (IOException e) {
				}
			}
		}

	}

	/**
	 * 格式校验
	 * 
	 * @param document
	 */
	private void validateXml(Document document) {
		Element root = document.getDocumentElement();

		// 校验版本号是否一致
		String version = root.getAttribute("version");
		if (!"1.0".equals(version)) {
			throw new IllegalArgumentException(
					"The dal config file format error. Please make sure to use the 1.0 version");
		}
	}

	/**
	 * 业务校验：详细验证配置内容是否有逻辑错误
	 * 
	 * @param dalConfig
	 */
	private void validateDalConfig(DalConfig dalConfig) {
		DataSourceConfig datasourceConfig = dalConfig.getDataSourceConfig();
		// 1.没配置主数据源，报警退出
		if (datasourceConfig.getMasterDatasourceConfig() == null) {
			throw new IllegalArgumentException("没有配置主数据源。请增加主数据源的配置");
		}
		// 2.假如只有一个数据源，而方法路由又配置了从的情况，报警退出
		if (datasourceConfig.getSlaveDataSourceConfigs() == null
				|| datasourceConfig.getSlaveDataSourceConfigs().size() == 0) {
			Collection<MethodRouterConfig> methodRouterConfigs = dalConfig.getMethodRouteConfigMap().values();
			for (MethodRouterConfig methodRouterConfig : methodRouterConfigs) {
				if (DataSourceType.Slave.equals(methodRouterConfig.getUse())) {
					throw new IllegalArgumentException("方法路由配置有误。只配置了一个主数据源，但是方法路由中配置了走从的方法。请增加从数据源配置或注释掉从方法路由");
				}
			}
		}
		// 3.检查类和方法是否存在，不存在则报警退出
		Collection<MethodRouterConfig> methodRouterConfigs = dalConfig.getMethodRouteConfigMap().values();
		for (MethodRouterConfig methodRouterConfig : methodRouterConfigs) {
			String className = methodRouterConfig.getClassName();
			String method = methodRouterConfig.getMethod();
			checkClassOrMethodHasAvailable(className, method);
		}

	}

	private void checkClassOrMethodHasAvailable(String className, String methodName) {
		try {
			Class clazz = Class.forName(className);
			if (MethodRouterConfig.DEFAULT_METHOD.equals(methodName)) {
				return;
			} else {
				if (ReflectionUtils.findMethod(clazz, methodName, null) == null) {
					throw new IllegalArgumentException(
							"读写分离配置，方法路由中配置的方法不存在，class=" + className + ",method=" + methodName);
				}
			}
		} catch (ClassNotFoundException e) {
			throw new IllegalArgumentException("读写分离配置，方法路由中配置的类不存在:" + className);
		}

	}

	private void parseApplicationInfo(Document document, DalConfig dalConfig) throws Exception {
		Element root = document.getDocumentElement();
		Element applicationInfoElement = XmlUtil.element(root, "application-info");
		if (applicationInfoElement != null) {
			String applicationName = XmlUtil.element(applicationInfoElement, "application-name").getTextContent();
			String applicationDesc = XmlUtil.element(applicationInfoElement, "application-desc").getTextContent();
			String applicationOwner = XmlUtil.element(applicationInfoElement, "application-owner").getTextContent();

			dalConfig.setApplicationInfo(new ApplicationInfo(applicationName, applicationDesc, applicationOwner));
		}
	}

	private void parseDatasourceConfig(Document document, DalConfig dalConfig) throws Exception {
		Element root = document.getDocumentElement();
		Element dataSourceConfigElement = XmlUtil.element(root, "data-source-config");

		List<Element> dataSourceList = XmlUtil.elements(dataSourceConfigElement, "data-source");

		DataSourceConfigItem masterDatasourceConfig = new DataSourceConfigItem();
		List<DataSourceConfigItem> slaveDataSourceConfigs = new ArrayList<>();
		for (Element dataSource : dataSourceList) {
			String id = dataSource.getAttribute("id");
			String mode = dataSource.getAttribute("mode");
			DataSourceType dataSourceType = DataSourceType.valueOf(mode);

			String dataSourceClass = dataSource.getAttribute("data-source-class");
			String initMethod = dataSource.getAttribute("init-method");
			String destroyMethod = dataSource.getAttribute("destroy-method");

			Map<String, String> poolPropertys = new HashMap<>();
			Element dataSourcePoolPropertysElement = XmlUtil.element(dataSource, "data-source-pool-propertys");
			List<Element> dataSourcePoolPropertys = XmlUtil.elements(dataSourcePoolPropertysElement, "property");
			for (Element property : dataSourcePoolPropertys) {
				poolPropertys.put(property.getAttribute("name"), property.getAttribute("value"));
			}

			// master
			if (DataSourceType.Master.equals(dataSourceType)) {
				masterDatasourceConfig = new DataSourceConfigItem(id, dataSourceType, dataSourceClass, initMethod,
						destroyMethod, poolPropertys);
			} else {
				// slave
				DataSourceConfigItem salveDatasourceConfigItem = new DataSourceConfigItem(id, dataSourceType,
						dataSourceClass, initMethod, destroyMethod, poolPropertys);
				slaveDataSourceConfigs.add(salveDatasourceConfigItem);
			}
		}

		// 默认数据源为主数据源
		String defauleDataSource = masterDatasourceConfig.getId();

		dalConfig.setDataSourceConfig(
				new DataSourceConfig(defauleDataSource, masterDatasourceConfig, slaveDataSourceConfigs));
	}

	private void parseMethodRouteConfigMap(Document document, DalConfig dalConfig) throws Exception {
		// method-router-config
		Element root = document.getDocumentElement();
		Element methodRouterConfigElement = XmlUtil.element(root, "method-router-config");

		List<Element> methodRouterConfigList = XmlUtil.elements(methodRouterConfigElement, "method-router");

		for (Element methodRouter : methodRouterConfigList) {
			String classname = methodRouter.getAttribute("classname");
			String method = methodRouter.getAttribute("method");
			if (method == null) {
				method = MethodRouterConfig.DEFAULT_METHOD;//*
			}
			String param = methodRouter.getAttribute("param");
			if (param == null) {
				param = MethodRouterConfig.DEFAULT_PARAM;//[*]
			}
			String use = methodRouter.getAttribute("use");
			DataSourceType dataSourceType = DataSourceType.valueOf(use);

			// 支持单条路由，多个方法配置。多方法名采用|分隔.
			// 支持*号模糊匹配，仅支持前缀型，即：find*，query*|find*|getUserInfo，*
			String[] methods = method.split("\\|");
			for (String m : methods) {
				if (!StringUtils.isEmpty(m)) {
					if (method.indexOf("*") >= 0) {
						// 支持前缀型的模糊匹配
						dalConfig.addFuzzyMethodRouteConfig(
								new MethodRouterConfig(classname, m, param, dataSourceType));
					} else {
						// 精确匹配
						dalConfig.addMethodRouteConfig(new MethodRouterConfig(classname, m, param, dataSourceType));
					}
				}
			}
		}
	}

}
